﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LiftEmulator.View;
using System.Threading;

namespace LiftEmulator.Model
{
    /// <summary>
    /// Здание
    /// </summary>
    public class Building
    {
        #region Поля и свойства класса
        
        /// <summary>
        /// Вероятность появления человека на этаже
        /// </summary>
        const double ProbabilityOfPeopleBorn = 0.05;   

        /// <summary>
        /// Генератор случайных чисел.
        /// Используется при создании людей.
        /// </summary>
        private Random random = new Random();

        /// <summary>
        /// Список этажей
        /// </summary>
        public Floor[] Floors { get; private set; }


        /// <summary>
        /// Лифт
        /// </summary>
        public Lift Lift { get; private set; }

        #endregion

        public Building(int numberOfFloor, int maxCapacityOfTheElevator)
        {
            Floors = Enumerable.Range(1, numberOfFloor).Select(number => new Floor(number)).ToArray();
            Lift = new Lift(this, maxCapacityOfTheElevator);
        }

        /// <summary>
        /// Главный цикл эмуляции.       
        /// </summary>
        public void RunEmulation()
        {
            while (true)
            {
                Lift.Step();                
                GenerationNewPeoplesOnFloors();

                Thread.Sleep(1000);
            }
        }

        #region Дополнительные методы и свойства

        /// <summary>
        /// Итератор для удобства работы с этажами
        /// </summary>
        /// <param name="number">номер этажа</param>
        /// <returns>этаж</returns>
        public Floor this[int number]
        {
            get
            {
                // нумерация этажей начинается с 1
                return Floors[number - 1];
            }
        }


        /// <summary>
        /// Генерация новых людей на этажах с заданной вероятностью.
        /// Вероятность задается константой ProbabilityOfPeopleBorn
        /// </summary>
        private void GenerationNewPeoplesOnFloors()
        {
            foreach (var floor in Floors)
            {                
                if (!floor.IsFull && random.NextDouble() < ProbabilityOfPeopleBorn)
                {
                    floor.Enqueue(new People(GetRandomTargetFloor(floor)));
                }
            }
        }


        /// <summary>
        /// Возвращает случайный этаж, кроме переданного
        /// Используется для определения этажа назначения для человека.
        /// </summary>
        /// <param name="current">текущий этаж</param>
        /// <returns>этаж назначения</returns>
        private Floor GetRandomTargetFloor(Floor current)
        {
            Floor next;
            do
            {
                next = Floors[random.Next(Floors.Length - 1)]; 
            } while (next == current);

            return next;
        }

        /// <summary>
        /// Получить следующий этаж, находящийся выше переданного.
        /// </summary>
        /// <param name="floor">этаж</param>
        /// <returns>следующий этаж</returns>
        public Floor UpFloor(Floor floor)
        {
            if (floor == Floors.Last())
            {
                throw new InvalidOperationException("Выше некуда");
            }

            // номера у этажей от 1, поэтому номер текущего - это индекс следующего
            var nextIndex = floor.Number;
            return Floors[nextIndex];
        }


        /// <summary>
        /// Получить следующий этаж, находящийся ниже переданного
        /// </summary>
        /// <param name="floor">этаж</param>
        /// <returns>следующий этаж</returns>
        public Floor DownFloor(Floor floor)
        {
            if (floor == Floors.First())
            {
                throw new InvalidOperationException("Ниже некуда"); 
            }

            // номера у этажей от 1, поэтому номер текущего - это индекс следующего 
            // (значит чтобы получить предыдущий, надо отнять 2)
            var nextIndex = floor.Number - 2;
            return Floors[nextIndex];
        }

        #endregion
    }
}
